iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 4
4
Mobile Development

如何用 Laravel 撰寫難以維護的專案系列 第 4

[Day 4] 混淆變數命名的利器!談匈牙利命名法

  • 分享至 

  • xImage
  •  

匈牙利命名法是過去的一種變數命名方式。簡單的說,藉由在變數的開頭加上一些字母的前綴,比方說是字串的 name 前面加上 str,變成 strName,來標註變數的形態(比方是字串),或者該特殊注意的地方(比方說,用 us 代表不安全字串)。

在過去,這可以讓維護的工程一看就知道這個變數的形態,或有哪些該特別注意的地方,是過去解決這些問題的一種設計方式。

到了現在,這個命名方式不僅用途不大,而且還可能造成後續維護的困擾。換句話說,非常符合我們建立難以維護專案的目的。

沒有什麼比起良好設計的匈牙利命名法,能更有效地殘害之後維護的工程師。如果我們在裡面,又時不時脫離原本匈牙利命名法設計的初衷,那效果就更好了。

以下技巧,可以保證你毀掉原本匈牙利命名法的初衷,為之後的工程師帶來無盡的痛苦:

常數

雖然 PHP 已經以大寫作常數的慣例,堅持使用 c 前綴代表常數。

例如,將

public const DUE_DATE = "02-28";

改成

public const c_duedate = "02-28";

類別

違背匈牙利命名法內,常用的變數應該要攜帶較少其他資訊的原則。

要達到這點,可以堅持每個類別都應該有自己的前綴。不讓別人提醒你針對什麼是類別這件事情,根本不存在匈牙利命名法前綴。

這個原則非常重要,如果沒有遵守,那程式碼可能會充斥許多簡短的,非常好唸的變數名稱。最差的狀況甚至可能導致整個混淆的努力失效,程式碼裡面出現用英文就可以看懂的段落!

比方,將

$user = User::find(1);
$post = Post::find(1);
public const UPDATE = 'update';

if ($user->can(UPDATE, $post)) {
    // 
}

改成

$suuser = User::find(1); // 第一個 s 代表 safe,u 代表這是一個 User 物件
$sppost = Post::find(1); // 第一個 s 代表 safe,p 代表這是一個 Post 物件
public const str_update = 'update'; // str 代表 字串
if ($suuser->can(str_update, $sppost)) {
    // 
}

m_

C++ 曾經有個命名慣例是使用 m_ 開頭的字代表成員(member)變數。

這個前綴可以幫助你區分儲存方法的變數,直到你想起方法(method)這個字也是 m 開頭。

o_apple

使用 oobj 前綴代表所有物件(object),代表你有看到多型結構的大局。

包含所有資訊

堅持在匈牙利命名法綴詞裡面,包含所有獨立的資訊。

參考這個曾經真實出現過的變數名稱 a_crszkvc30LastNameCol

花費一整個團隊的維護工程師大概三天的時間,才搞清楚這個變數是一個常數(c),參考(reference),函式的引數(a_),null 結尾字串(sz),某個資料庫欄位(Col)裡面名為 LastName 的資料,資料長度是 Varchar[30](vc30),而且是某個表的 primary key 裡面的一部分(k)。

搭配「所有變數都是公開的」原則,這個技巧可以讓數千行程式碼直接廢掉!


上一篇
[Day 3] 從變數命名開始,讓專案程式碼難以維護
下一篇
[Day 5] 從函式架構建立難以維護的程式碼
系列文
如何用 Laravel 撰寫難以維護的專案30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

0
小碼農米爾
iT邦高手 1 級 ‧ 2020-09-04 20:50:37

最後的 a_crszkvc30LastNameCol 太精采了!! ((吃瓜群眾路過 /images/emoticon/emoticon35.gif

0
taiansu
iT邦新手 4 級 ‧ 2020-09-17 12:58:57

文章很有趣~ 但匈牙利命名法本身是很好的。只是在誤用跟縮寫的情況下被污名化了。

可以看一下 Joel Spolsky 十五年前的這篇文章。如果你沒聽過這個人的話,提示一下他是 Excel VBA 的規格制訂者,跟 Stackoverflow 的創辦人(之一)。

https://www.csie.ntu.edu.tw/~p92005/Joel/Wrong.html

看更多先前的回應...收起先前的回應...
ReccaChao iT邦新手 1 級 ‧ 2020-09-17 14:34:25 檢舉

感謝回應,我知道這個人,也知道這篇文章。
這篇文章我自己看到的重點是

應用匈牙利命名法非常有用,特別是當初C語言盛行,而編譯器尚未提供很有用的型別系統時。

現在的 IDE 已經可以很好的提供有用的型別系統。隨著語言的進化,更長的變數不再是問題,Joel 文章裡面的

利用這套編碼規範,你的眼睛學著看到Write usXXX就知道是錯的,而且你也立即知道要如何修正。

也已經可以變成

「眼睛學著看到 Write unsafeXXX 就知道是錯的」,不需要特別的編碼規範。

taiansu iT邦新手 4 級 ‧ 2020-09-17 14:41:34 檢舉

感謝回應。同意你所說的,但是「用 unsafe 來 prefix 變數」這件事就是一種編碼規範啊 XD

ReccaChao iT邦新手 1 級 ‧ 2020-09-17 16:26:51 檢舉

unsafe 來 prefix 變數,是以命名的方式,來標記這個變數該特殊注意的地方。

對一般的工程來說,他只要看到 unsafeXXXX 就應該要知道這個變數是「不安全」的(除非他看不懂「unsafe」的意思,這是其他問題,這裡先不討論)。

但是一個工程看到 usXXX,他並不會知道這裡 us 的前綴是什麼意思。必須要透過其他人的告知,或者文件的資訊,他才會知道 us 代表「不安全」。

借用 Joel 文章裡面舉例

我花了好幾個月每天早上打掃才真正瞭解他們的意思。對麵包工廠來說,乾淨是指機器裡沒有生麵糰在烤,垃圾堆裡沒有發酵的麵糰,而且地板上也沒有堆生麵糰。

麵包工廠的乾淨,不是一般人所認知的乾淨,這是不可避免的,所以你需要訓練才會知道。

但是,現在要知道一個變數該特殊注意的地方,你可以從變數的命名直接得到資訊,這是工程通用的做法,而不再是單一專案或團體的做法。

這也是我上面提到「『眼睛學著看到 Write unsafeXXX 就知道是錯的』,不需要特別的編碼規範。」的意思:你不需要特別去記 us 代表 unsafe,你直接寫 unsafe 就好了。

taiansu iT邦新手 4 級 ‧ 2020-09-17 17:53:24 檢舉

啊。我覺得是我沒有講清楚。

如果用文章裡的範例,大家說好要用某些字要讓系統內的程式碼看得出錯,並讓名稱配合變數名稱,那些說好變數跟函數要配合,以及選用哪些字就是個這個應用程式裡的編碼規範。因為如果不說好,大家就會各自用各自的字了,甚或根本不 prefix 了。

例如說大家講好用 safe 來 prefix 好了,那麼這個規範就是要改成:

unsafe = UnsafeRequest("name")
safeName = SafeFromUnsafe(recordset("unsafeName"))

那麼在不縮寫的情況下,文中另一個視窗座標例子就會變成:

YpointLayout = YpointLayoutFromYpointWindow(XPointWindow) #錯的

除了很長更難讀懂之外,真正的造成麻煩是變數的意義跟防錯標識會混在一起,反而更不容易看。

其實我想表達的立場可以簡單這樣講:「有些團隊了解匈牙利命名法的正確用法及好處,不需要污名化它。而應用程式內的 meta knowledge 是必然存在的。一部份可以靠編碼規範緩解,但有一部份非得用溝通解決。」

ReccaChao iT邦新手 1 級 ‧ 2020-09-18 06:59:11 檢舉

大概理解你的意思,我也覺得確實如此:應用程式內的 meta knowledge 是必然存在的。一部份可以靠編碼規範緩解,但有一部份非得用溝通解決。

YpointLayout = YpointLayoutFromYpointWindow(XPointWindow)

這種寫法也確實不好讀懂。


文中的視窗座標例子的話,我想引用一下原文:

So, to tell you the truth, I’ve never seen the Word source code, but I’ll bet you dollars to donuts there’s a function called YlFromYw which converts from vertical window coordinates to vertical layout coordinates.

以這段話為出發點,我們可以比較看看:

VerticalLayoutFromVerticalWindow() 是否比 YlFromYw()需要的溝通更少?

VerticalLayout = VerticalLayoutFromVerticalWindow(HorizontalWindow)

是否比起

Yl = YlFromYw(Xw)

更易讀?

一旦 VerticalLayout 的轉換出錯要維護,是要找到所有的
VerticalLayout 容易,還是找到所有的 Yl 容易?

這幾點或許是可以想想的。

taiansu iT邦新手 4 級 ‧ 2020-09-18 09:48:03 檢舉

我覺得 Yl = YlFromYw(Xw) 比較容易「一眼看出錯誤」,而且 考慮到整個程式的屬性, 一定有很多跟方向及視窗有關的變數。YL 跟 YW 重複率不會比 Vertical、Layout 這種字高太多。(笑)

當然換字是種選擇,不過立場還是一樣的,特別是前半段:「不需要污名化縮寫 prefix 這件事。」

ReccaChao iT邦新手 1 級 ‧ 2020-09-18 10:49:54 檢舉

大概可以理解你的立場,不過這裡並沒有要污名化匈牙利命名法或縮寫 prefix 這幾件事情。

匈牙利命名法是針對過去難以馬上知道變數的形態,或難以馬上知道該特別注意的地方,所設計的一套方式。他的創造者跟內容,在當時都是有意義的。

可是在現代,IDE 可以協助你知道變數的形態,較長的命名和註解可以協助你知道該特別注意的地方,所以我的文章說「到了現在,這個命名方式不僅用途不大,而且還可能造成後續維護的困擾。」。

YL、YW、Vertical、Layout 重複率的部分,我搜尋了一下 Laravel 8 全新專案個別出現的地方,或許可以參考看看。

YL

YW

Vertical

Layout

我要留言

立即登入留言